//inválido
#define INVALID 0 
//menos recentemente utilizado e não modificado
#define LRU_NMOD 1
//mais recentemente utilizado e não modificado
#define NLRU_NMOD 3
//menos recentemente utilizado e modificado
#define LRU_MOD 5
//mais recentemente utilizado e modificado
#define NLRU_MOD 7
//bits do byte offset
#define BYTE_OFFSET 2
//numero de bits para controlar as palavras da cache
#define WORD_OFFSET 1
//deslocamento do conjunto para poder pegar a tag
#define SET 3
//mascara para pegar os bits do conjunto
#define MASK_SET 7 

typedef struct
{
	int w0;
	int w1;
}BLOCO_RAM;

// int memory_read(int addr, int *data, int *word, int *b_RAM, int *set, int *tag, int *b_cache);
// void calculation(int addr, int *word, int *b_RAM, int *set, int *tag);
// int write_cache(int addr, int *data);
// void write_memory(BLOCO_RAM b, int set, int tag);
// int memory_access( int addr,  int *data,  int type );


//calcula o conjunto onde o dado será inserido
void calculation(int addr, int *word, int *b_RAM, int *set, int *tag)
{
    addr=addr>>BYTE_OFFSET;//deslocamento do byte offset

    //pegando os bits da word
    *word=addr & WORD_OFFSET;

    addr=addr>>WORD_OFFSET;//deslocamente da word offset
    
    //pegando o bloco
    *b_RAM=addr;
    
    //agora pode-se pegar o conjunto
	
    //pegando os bits do set
    *set=addr & MASK_SET;

    //deslocamento do conjunto
    //para podermos pegar a tag
    addr=addr>>SET;

    //pegando a tag
    *tag=addr;

}

//escreve na memória RAM
void write_memory(BLOCO_RAM b, int set, int tag)
{
   int addr_bloco=0, pos;
   //montando o endereco do bloco
   addr_bloco=tag<<SET;
   addr_bloco=addr_bloco | set;
   //calcula a posicao da primeira palavra do bloco na RAM
   pos=addr_bloco*2;
   memory[pos]=b.w0;
   memory[pos+1]=b.w1;
   
}


//função de leitura da memória
void memory_read(int addr, int *data, int *word, int *b_RAM, int *set, int *tag, int *b_cache)
{       
        //calculando o conjunto do endereço
        calculation(addr, word, b_RAM, set, tag);
        int pos= (*b_RAM)*2;//primeira posição do bloco
        BLOCO_RAM Bloco_RAM;
        //carrega o bloco na struct
        Bloco_RAM.w0=memory[pos];
	Bloco_RAM.w1=memory[pos+1];
	//agora iremos verificar se o dado já está na cache, no bloco 0
        if(cache[*set].b0_ctl!=INVALID && *tag==cache[*set].b0_tag)
        {
	    *b_cache=0;
            if(*word==0)//o dado já está na cache, no bloco 0, na palavra 0
                *data=cache[*set].b0_w0;
            else
            if(*word==1)//o dado ja está na cache, no bloco 0, na palavra 1
                *data=cache[*set].b0_w1;
        }//não está no bloco 0
        else//procura no bloco 1
        if(cache[*set].b1_ctl!=INVALID && *tag==cache[*set].b1_tag)
        {
	    *b_cache=1;
            if(*word==0)//o dado já está na cache, no bloco 1, na palavra 0

                *data=cache[*set].b1_w0;
            
            else
            if(*word==1)//o dado ja está na cache, no bloco 1, na palavra 1
                *data=cache[*set].b1_w1;
        }//não está no bloco 1
        else//não está na cache
	if(cache[*set].b0_ctl==INVALID || cache[*set].b0_ctl==LRU_NMOD)
        {
            //dados na cache são inválidos, ou o bloco é o LRU nao modificado
            //então iremos sobrescrever o dado
            *b_cache=0;
            //atualiza os bits de controle do bloco 0
            cache[*set].b0_ctl=NLRU_NMOD;
            cache[*set].b0_tag= *tag;
            //atualiza os bits de controle do bloco 1
            //atualiza o bit de LRU com 0 assim o bloco 1 passa a ser LRU
            cache[*set].b1_ctl=cache[*set].b1_ctl & 5;
            cache[*set].b0_w0=Bloco_RAM.w0;
	    cache[*set].b0_w1=Bloco_RAM.w1;
            switch(*word)
            {
                case 0://caso seja a primeira palavra
                {
		    *data=cache[*set].b0_w0;
                    break;
                }
                case 1://caso seja a segunda palavra
                {
                    *data=cache[*set].b0_w1;
                    break;
                }
            }
        }
        else
        if(cache[*set].b0_ctl==LRU_MOD)
        {
            *b_cache=0;
	    BLOCO_RAM aux;
	    //carregando os dados para escrever na RAM
	    aux.w0=cache[*set].b0_w0;
	    aux.w1=cache[*set].b0_w1;
            //tem que escrever na memória RAM...
	    write_memory(aux, *set, cache[*set].b0_tag);
	    //carregando os dados na cache
	    cache[*set].b0_ctl=NLRU_NMOD;
            cache[*set].b0_tag= *tag;
            //atualiza os bits de controle do bloco 1
            //atualiza o bit de LRU com 0 assim o bloco 1 passa a ser LRU
            cache[*set].b1_ctl=cache[*set].b1_ctl & 5;
	    //carrega os dados da ram para a cache
            cache[*set].b0_w0=Bloco_RAM.w0;
	    cache[*set].b0_w1=Bloco_RAM.w1;
            switch(*word)
            {
                case 0://caso seja a primeira palavra
                {
		    *data=cache[*set].b0_w0;
                    break;
                }
                case 1://caso seja a segunda palavra
                {
                    *data=cache[*set].b0_w1;
                    break;
                }
            }
	    
        }
        else
        if(cache[*set].b1_ctl==INVALID || cache[*set].b1_ctl==LRU_NMOD)
        {   //o bloco 1 é inválido ou LRU
            //dados na cache são inválidos, ou o bloco é o LRU nao modificado
            //então iremos sobrescrever o dado
            *b_cache=1;//controla o bloco que está o dado
            cache[*set].b1_ctl=NLRU_NMOD;
            cache[*set].b1_tag= *tag;
            //atualiza os bits de controle do bloco 1
            //atualiza o bit de LRU com 0 assim o bloco 1 passa a ser LRU
            cache[*set].b0_ctl=cache[*set].b0_ctl & 5;
            cache[*set].b1_w0=Bloco_RAM.w0;
	    cache[*set].b1_w1=Bloco_RAM.w1;
            switch(*word)
            {
                case 0://caso seja a primeira palavra
                {
                    *data=cache[*set].b1_w0;
                    break;
                }
                case 1://caso seja a segunda palavra
                {
                    *data=cache[*set].b1_w1;
                    break;
                }
            }
        }
        else
        if(cache[*set].b1_ctl==LRU_MOD)
        {
            *b_cache=1;
	    BLOCO_RAM aux;
	    //carregando os dados para escrever na RAM
	    aux.w0=cache[*set].b1_w0;
	    aux.w1=cache[*set].b1_w1;
            //tem que escrever na memória RAM...
	    write_memory(aux, *set, cache[*set].b1_tag);
	    
	    //carregando os dados na cache
            cache[*set].b1_tag= *tag;
            //atualiza os bits de controle do bloco 1
	    cache[*set].b1_ctl=NLRU_NMOD;
            //atualiza o bit de LRU com 0 assim o bloco 0 passa a ser LRU
            cache[*set].b0_ctl=cache[*set].b0_ctl & 5;
            cache[*set].b1_w0=Bloco_RAM.w0;
	    cache[*set].b1_w1=Bloco_RAM.w1;
            switch(*word)
            {
                case 0://caso seja a primeira palavra
                {
                    *data=cache[*set].b1_w0;
                    break;
                }
                case 1://caso seja a segunda palavra
                {
                    *data=cache[*set].b1_w1;
                    break;
                }
            }

        }
}

//escreve na memória CACHE
void write_cache(int addr, int *data)
{
    int word, b_RAM, set, tag, b_cache, aux;
    memory_read(addr, &aux, &word, &b_RAM, &set, &tag, &b_cache);
    if(b_cache==0)
    {
        //escreve na memoria cache
        if(word==0)
            cache[set].b0_w0= *data;
        else
            cache[set].b0_w1= *data;
        //atualiza os bits de controle do bloco 0
        cache[set].b0_ctl=NLRU_MOD;
        //atualiza os bits de controle do bloco 1
        cache[set].b1_ctl=cache[set].b1_ctl & 5;
    }
    else
    if(b_cache==1)
    {
        //escreve na memoria cache
        if(word==0)
            cache[set].b1_w0= *data;
        else
            cache[set].b1_w1= *data;
        //atualiza os bits de controle do bloco 1
        cache[set].b1_ctl=NLRU_MOD;
        //atualiza os bits de controle do bloco 0
        cache[set].b0_ctl=cache[set].b0_ctl & 5;

    }

}

//função de acesso a memória
int memory_access( int addr,  int *data,  int type )
{	
	int word, b_RAM, set, tag, b_cache;
	if(type==0)//acesso do tipo leitura
	{
		memory_read(addr, data, &word, &b_RAM, &set, &tag, &b_cache);
		return 1;
	}
	else
	if(type==1)//acesso do tipo de escrita
	{
		write_cache(addr, data);
		return 1;
	}
	else
		return -1;

}